﻿/***********************************************************************************\
| Copyright (c) 2011, Bashar Al-Rawi                                                |
| All rights reserved.                                                              |
|                                                                                   |
| Redistribution and use in source and binary forms, with or without                |
| modification, are permitted provided that the following conditions are met:       |
|    * Redistributions of source code must retain the above copyright               |
|      notice, this list of conditions and the following disclaimer.                |
|    * Redistributions in binary form must reproduce the above copyright            |
|      notice, this list of conditions and the following disclaimer in the          |
|      documentation and/or other materials provided with the distribution.         |
|    * Neither the name of the <organization> nor the                               |
|      names of its contributors may be used to endorse or promote products         |
|      derived from this software without specific prior written permission.        |
|                                                                                   |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND   |
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED     |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE            |
| DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY                |
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;      |
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND       |
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT        |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS     |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                      |
\***********************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Arabic.Utils;
using System.IO.IsolatedStorage;
using System.Xml.Serialization;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;

namespace Arabic.Controls
{
    public partial class RTLTextBlock : UserControl
    {
        internal SuperString InternalText { get; set; }
        private IList<TextBlock> textBlocks = new List<TextBlock>();
        private static Brush blackBrush = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
        private int MaxLineWidth
        {
            get {
                double maxWidth = int.MaxValue;
                if (this.Width > 0)
                {
                    maxWidth = this.MaxWidth > this.Width? this.Width: this.MaxWidth;
                } else
                {
                    maxWidth = this.MaxWidth > 0? MaxWidth: int.MaxValue;
                }
                return (int) maxWidth; 
            }
        }
        private int currentTextBlockIdx = -1;

        internal TextBlock CurrentTextBlock
        {
            get
            {
                return textBlocks[currentTextBlockIdx];
            }
        }

        private void ClearExtraTextBlocks()
        {
            for (int i = textBlocks.Count - 1; i > currentTextBlockIdx; i--)
            {
                textBlocks.RemoveAt(i);
                textBlocksStackPanel.Children.RemoveAt(i);
            }
        }

        private void ClearTextBlocks(string text)
        {
            if (currentTextBlockIdx == -1)
                return;

            if (string.IsNullOrEmpty(text))
            {
                this.CurrentTextBlock.Foreground = blackBrush;
                this.CurrentTextBlock.Text = text;
            }
            while (currentTextBlockIdx >= 0)
            {
                this.CurrentTextBlock.Text = "";
                currentTextBlockIdx--;
            }
            
        }

        private void AddTextBlock()
        {
            currentTextBlockIdx++;
            if (currentTextBlockIdx >= textBlocks.Count)
            {
                TextBlock tb = new TextBlock();
                if (this.FlowDirection == Utils.FlowDirection.RightToLeft)
                    tb.TextAlignment = TextAlignment.Right;
                else
                    tb.TextAlignment = TextAlignment.Left;
                tb.Width = this.Width;
                tb.MinHeight = 30;
                textBlocks.Add(tb);
                textBlocksStackPanel.Children.Add(tb);
            }
        }
        
        public RTLTextBlock()
		{
            // Required to initialize variables
            InitializeComponent();
            InternalText = new SuperString();
            this.LayoutRoot.Background = this.Background;//new SolidColorBrush(Color.FromArgb(255,100,100,0));
            this.MaxWidth = 460;
		}

        public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
          "TextWrapping", typeof(TextWrapping), typeof(RTLTextBlock), new PropertyMetadata(TextWrapping.Wrap, OnWrappingPropertyChanged));
        public TextWrapping TextWrapping
        {
            get { 
                TextWrapping tw = (TextWrapping)this.GetValue(TextWrappingProperty);
                return tw;
            }
            set { this.SetValue(TextWrappingProperty, value); }
        }

        public static readonly DependencyProperty FlowDirectionProperty = DependencyProperty.Register(
          "FlowDirection", typeof(FlowDirection), typeof(RTLTextBlock), new PropertyMetadata(FlowDirection.RightToLeft, OnFlowDirectionPropertyChanged));
        public FlowDirection FlowDirection
        {
            get { return (FlowDirection)this.GetValue(FlowDirectionProperty); }
            set { this.SetValue(FlowDirectionProperty, value); }
        }


        ///// <summary> 
        ///// Gets or sets the TextAlignment possible Value of the TextAlignment object.
        ///// </summary> 
        //public static readonly DependencyProperty TextAlignmentProperty =DependencyProperty.Register(
        //                  "TextAlignment", typeof(TextAlignment), typeof(RTLTextBlock), null);
        //[DefaultValue(TextAlignment.Right)]
        //[Category("Text"), Description("Specifies the alignment of text.")]
        //public TextAlignment TextAlignment
        //{
        //    get { return (TextAlignment)GetValue(TextAlignmentProperty); }
        //    set { SetValue(TextAlignmentProperty, value); }
        //}
       
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();  
        }

        public static readonly DependencyProperty TextProperty 
            = DependencyProperty.Register("Text", typeof(string), typeof(RTLTextBlock), 
            new PropertyMetadata(new PropertyChangedCallback(RTLTextBlock.OnTextPropertyChanged)));
        public string Text
        {
            get{ return (string) GetValue(TextProperty); }
            set{ SetValue(TextProperty, value);}
        }
        private void NoWrapText()
        {
            string text = InternalText.DisplayText;
            ClearTextBlocks(text);
            AddTextBlock();
            string reversedLine = StringHelper.ReverseString(text);
            this.CurrentTextBlock.Text = reversedLine;                
        }

        private void WrapText()
        {
            //Debug.Assert(this.MaxLineWidth > 0);
            string text = InternalText.DisplayText;
            ClearTextBlocks(text);
            int startIdx = 0;
            int newIdx = 0;
            string word = null;
            StringBuilder sb = new StringBuilder();
            AddTextBlock();
            while ((word = StringHelper.GetNextWord(text, startIdx, out newIdx)) != null)
            {
                int currentSize = sb.Length;
                bool appendNewLine = false;
            
                if (word.EndsWith("\r"))
                {
                    word = word.Substring(0, word.Length - 1);
                    appendNewLine = true;
                }
                sb.Append(word);
                string reversedLine = this.FlowDirection == Utils.FlowDirection.RightToLeft ? StringHelper.ReverseString(sb.ToString()) : sb.ToString();
                this.CurrentTextBlock.Text = reversedLine;
                if (this.CurrentTextBlock.ActualWidth > MaxLineWidth)
                {
                    int spaceIdx = sb.ToString().LastIndexOf(' ', sb.Length - 2); // ignore space if last char
                    if (spaceIdx == -1) // there is one word from the beginning of the line to end
                    {
                        while (this.CurrentTextBlock.ActualWidth > MaxLineWidth)
                        {
                            sb.Remove(sb.Length - 1, 1);
                            this.CurrentTextBlock.Text = sb.ToString();
                        }

                        startIdx = sb.Length - 1 + startIdx;
                    }
                    else
                    {
                        sb.Remove(spaceIdx, sb.Length - spaceIdx);
                    }
                    // clear append new line to avoid double line wrapping
                    appendNewLine = false;
                    reversedLine = StringHelper.ReverseString(sb.ToString());
                    this.CurrentTextBlock.Text = reversedLine;
                    sb = new StringBuilder();
                    AddTextBlock();
                }
                else
                {
                    startIdx = newIdx;
                }
                if (appendNewLine)
                {
                    sb = new StringBuilder();
                    AddTextBlock();
                }
                
            }
            ClearExtraTextBlocks();
        }

        public void ResetTextData()
        {
            if (this.TextWrapping == TextWrapping.Wrap && !string.IsNullOrEmpty(this.InternalText))
            {
                WrapText();
            }
            else
            {
                NoWrapText();
            }
        }

        private static void OnWrappingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RTLTextBlock text = d as RTLTextBlock;
            text.ResetTextData();
        }

        private static void OnFlowDirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RTLTextBlock text = d as RTLTextBlock;
            foreach (TextBlock tb in text.textBlocks)
            {
                if (((FlowDirection)e.NewValue) == FlowDirection.RightToLeft)
                {
                    tb.TextAlignment = TextAlignment.Right;
                }
                else
                {
                    tb.TextAlignment = TextAlignment.Left;
                }
            }
        }

        

        private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RTLTextBlock text = d as RTLTextBlock;
            text.InternalText = e.NewValue.ToString();
            text.ResetTextData();
        }

        private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
        {
            this.LayoutRoot.Background = this.Background;
        }

        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            foreach (TextBlock tb in this.textBlocks)
            {
                tb.Width = this.Width;
                tb.MaxWidth = this.MaxWidth;
            }
            
        }
    }
}
